﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using DataScripter.Database;
using DataScripter.Scripting;
using DataScripter.Scripting.Fluent;
using DataScripter.Scripting.InputSources;
using DataScripter.Scripting.Output;
using DataScripter.Scripting.OutputDestinations;
using DataScripter.Scripting.ScriptDefinitions;
using DataScripter.XmlInterpreter;

namespace DataScripter.UI.Old
{
    // ReSharper disable UnusedMember.Global
    class XmlEngineConfiguratorv2 : IXmlEngineConfigurator
    // ReSharper restore UnusedMember.Global
    {
        private IEngine Engine { get; set; }

        public IEngine Configure(IEngine engine, XmlDocument document, ScriptOptions scriptOptions)
        {
            Engine = engine;

            XmlElement script = document.DocumentElement;
            if (script == null)
            {
                return engine;
            }

            string connectionString = script.GetAttribute("ConnectionString");
            string outputPath = script.GetAttribute("OutputPath");

            scriptOptions.ScriptOutputType = ScriptOutputType.File;
            scriptOptions.Filename = outputPath;

            SqlServerInputSource inputSource = new SqlServerInputSource("Database", engine);
            engine.RegisterType<IInputSource>("a", inputSource);
            engine.RegisterType<ISqlServerInputSource>("b", inputSource);
            inputSource.ConnectionString = connectionString;

            FileOutputDestination outputDestination = new FileOutputDestination("", engine);
            engine.RegisterType<IOutputDestination>(outputDestination);

            IScriptDefinition scriptDefinition = new ScriptDefinition("", engine);
            engine.RegisterType(scriptDefinition);
            

            foreach (XmlElement element in script.ChildNodes.OfType<XmlElement>())
            {
                switch (element.Name)
                {
                    case "DataProducer":
                        RegisterType<IDataProducer>(element);
                        break;
                    case "OutputProducer":
                        RegisterType<IOutputProducer>(element);
                        break;
                    case "Db":
                        RegisterType<Db>(element);
                        break;
                    case "DatabaseStructure":
                        RegisterType<IDatabaseStructure>(element);
                        break;
                    case "ScriptRequest":
                        InterpretScriptRequest(element, scriptDefinition, inputSource);
                        break;
                }
            }


            return engine;
        }

        private void InterpretScriptRequest(XmlElement element, IScriptDefinition scriptDefinition, IInputSource inputSource)
        {
            foreach (XmlElement child in element.ChildNodes.OfType<XmlElement>())
            {
                switch (child.Name)
                {
                    case "Table":
                        InterpretTable(child, null, scriptDefinition, inputSource);
                        break;
                }
            }
        }

        private void InterpretTable(XmlElement element, IFluentTableScriptRequest parent, IScriptDefinition scriptDefinition, IInputSource inputSource)
        {
            string tableName = element.GetAttribute("Name");
            string whereClause = element.GetAttribute("Where");
            IFluentTableScriptRequest scriptRequest = scriptDefinition.RequestScript(tableName);
            if (!string.IsNullOrEmpty(whereClause))
            {
                scriptRequest.Where(whereClause);
            }
            if (parent != null)
            {
                string joinOn = element.GetAttribute("JoinOn");
                if (string.IsNullOrEmpty(joinOn))
                {
                    // find foreign key to relate the parent with the child
                    IForeignKey key = FindUniqueForeignKey(scriptRequest.Request, parent.Request);
                    if (key != null)
                    {
                        scriptRequest.FilterByForeignKey(key);
                    }
                }
                else
                {
                    scriptRequest.Where(string.Format("{0} IN (SELECT {0} FROM {1} WHERE {1}.{0}={2}.{0} AND {3})", joinOn, parent.Request.TableName, scriptRequest.Request.TableName, parent.Request.ProduceFilter()));
                }
            }
            foreach (XmlElement child in element.ChildNodes.OfType<XmlElement>())
            {
                switch (child.Name)
                {
                    case "Table":
                        InterpretTable(child, scriptRequest, scriptDefinition, inputSource);
                        break;
                    case "Or":
                        IFluentOrFilterRequest or = scriptRequest.BeginOr();
                        scriptRequest = InterpretOrElement(child, or, parent);
                        break;
                    case "Where":
                        whereClause = child.GetAttribute("Clause");
                        scriptRequest.Where(whereClause);
                        break;
                    case "FilterByForeignKey":
                        string foreignKeyName = child.GetAttribute("Name");
                        string foreignTable = child.GetAttribute("OnTable");
                        ITableStructure tableStructure = inputSource.Resolve<IDatabaseStructure>().GetTable(string.IsNullOrEmpty(foreignTable) ? tableName : foreignTable);
                        IForeignKey foreignKey = tableStructure.ForeignKeys.Single(fk => fk.Name.ToUpper() == foreignKeyName.ToUpper());
                        if (foreignKey != null)
                        {
                            scriptRequest.FilterByForeignKey(foreignKey);
                        }
                        break;
                    case "Lookup":
                        string fieldName = child.GetAttribute("Name");
                        string lookupTable = child.GetAttribute("LookupTable");
                        string lookupField = child.GetAttribute("LookupField");
                        scriptRequest.Lookup(fieldName, lookupTable, lookupField);
                        break;
                }
            }
            return;
        }

        private static IForeignKey FindUniqueForeignKey(ITableScriptRequest child, ITableScriptRequest parent)
        {
            IEnumerable<IForeignKey> fksChild2Parent = child.TableStructure.ForeignKeys.Where(fk => fk.TableName == child.TableName && fk.ReferencedTableName == parent.TableName);
            IEnumerable<IForeignKey> fksParent2Child = parent.TableStructure.ForeignKeys.Where(fk => fk.TableName == parent.TableName && fk.ReferencedTableName == child.TableName);
            IEnumerable<IForeignKey> allForeignKeys = fksChild2Parent.Union(fksParent2Child);
            List<IForeignKey> allForeignKeysList = allForeignKeys.ToList();
            return allForeignKeysList.Count == 1 ? allForeignKeysList[0] : null;
        }

        private void RegisterType<T>(XmlElement element)
        {
            string typeDescriptor = element.GetAttribute("Type");
            string name = element.GetAttribute("Name");
            if (string.IsNullOrEmpty(name))
            {
                Engine.RegisterType<T>(Type.GetType(typeDescriptor));
            }
            else
            {
                Engine.RegisterType<T>(name, Type.GetType(typeDescriptor));
            }
        }

        private static IFluentTableScriptRequest InterpretOrElement(XmlElement element, IFluentOrFilterRequest or, IFluentTableScriptRequest parent)
        {
            foreach (XmlElement childElement in element.ChildNodes.OfType<XmlElement>())
            {
                switch (childElement.Name)
                {
                    case "FilterByForeignKey":
                        string foreignKeyName = childElement.GetAttribute("Name");

                        // this one might not exist
                        IForeignKey foreignKey = or.Request.TableStructure.ForeignKeys.SingleOrDefault(fk => fk.Name.ToUpper() == foreignKeyName.ToUpper()) 
                            // then this one should
                            ?? parent.Request.TableStructure.ForeignKeys.Single(fk => fk.Name.ToUpper() == foreignKeyName.ToUpper());

                        if (foreignKey.TableName != or.Request.TableName)
                            foreignKey = foreignKey.CreateReverseInMemoryForeignKey();
                        or.FilterByForeignKey(foreignKey);
                        break;
                    case "Where":
                        string whereClause = childElement.GetAttribute("Clause");
                        or.Where(whereClause);
                        break;
                }
            }
            return or.EndOr();
        }
    }
}